package org.groovymud.object.registry;

import org.groovymud.object.MudObject;

import java.util.*;

import static java.util.Collections.synchronizedMap;
import static java.util.Collections.synchronizedSet;
import static java.util.UUID.randomUUID;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

/**
 * The inventory handler is a TreeMap backed mud object management system The
 * handler registers the objects using names and shortnames determined from the
 * mud object its self. It stores each object in a Set.
 * <p/>
 * When the Set is empty, the set and its key is removed.
 */
public class InventoryHandler {

    private Map<String, Set<MudObject>> mudObjectHashSets = synchronizedMap(new TreeMap<String, Set<MudObject>>());

    public void addMudObject(MudObject object) {
        String internalId = randomUUID().toString();
        addMudObject(internalId, object);

        addObjectUsingName(object);
        addObjectShortNames(object);
    }

    private void addObjectUsingName(MudObject object) {
        String objectName = object.getName();
        if (objectName != null) {
            addMudObject(objectName, object);
        }
    }

    private void addObjectShortNames(MudObject object) {
        if (object.getShortNames() != null) {
            for (int x = 0; x < object.getShortNames().size(); x++) {
                addMudObject(object.getShortNames().toArray()[x].toString(), object);
            }
        }
    }

    protected void addMudObject(String key, MudObject object) {
        addMudObjectToMap(key, object, getMudObjectHashSets());
    }

    private Map<String, Set<MudObject>> addMudObjectToMap(String key, MudObject object, Map<String, Set<MudObject>> mudObjectHashSets) {
        Set<MudObject> objectHashSet = findOrCreateSetUsingHandle(key, mudObjectHashSets);
        objectHashSet.add(object);
        mudObjectHashSets.put(key, objectHashSet);
        return mudObjectHashSets;
    }

    private Set<MudObject> findOrCreateSetUsingHandle(String key, Map<String, Set<MudObject>> mudObjectHashSets) {
        Set<MudObject> objectHashSet;
        if (mudObjectHashSets.get(key) != null) {
            objectHashSet = mudObjectHashSets.get(key);
        } else {
            objectHashSet = new HashSet<MudObject>();
        }
        return objectHashSet;
    }

    /**
     * returns the first object found with the given name
     *
     * @param name - name of the object
     */
    public MudObject getMudObject(String name) {
        Set<MudObject> mudObjectSet = getMudObjects(name);
        MudObject mudObject = null;
        if (mudObjectSet != null) {
            ArrayList<MudObject> mudObjects = new ArrayList<MudObject>(mudObjectSet);
            mudObject = mudObjects.get(0);
        }
        return mudObject;
    }


    /**
     * @param name - mud object name
     * @return Set of the HashSet of mudobjects that have that name
     */
    public Set<MudObject> getMudObjects(String name) {
        return getMudObjectHashSets().get(name);
    }

    /**
     * gets all mud objects not backed by the registry
     *
     * @return a new Set containing all MudObjects.
     */
    public Set<MudObject> getMudObjects() {
        Set<MudObject> allObjects = synchronizedSet(new HashSet<MudObject>());
        Map<String, Set<MudObject>> objectHashSets = getMudObjectHashSets();

        synchronized (objectHashSets) {
            Collection<Set<MudObject>> values = objectHashSets.values();
            for (Set<MudObject> value : values) {
                allObjects.addAll(value);
            }
        }
        return allObjects;
    }

    public Map<String, Set<MudObject>> getMudObjectsMap() {
        Map<String, Set<MudObject>> objs = createMap();

        final Map<String, Set<MudObject>> objectHashSets = getMudObjectHashSets();
        synchronized (objectHashSets) {
            Collection<String> keySet = objectHashSets.keySet();
            Iterator<String> keySetIterator = keySet.iterator();
            while (keySetIterator.hasNext()) {
                String key = keySetIterator.next();
                Set<MudObject> set = objectHashSets.get(key);
                if (set != null) {
                    Iterator<MudObject> setI = set.iterator();
                    while (setI.hasNext()) {
                        MudObject x = setI.next();
                        addMudObjectToMap(x.getName(), x, objs);
                    }
                }
            }
        }
        return objs;
    }

    protected Map<String, Set<MudObject>> createMap() {
        return synchronizedMap(new TreeMap<String, Set<MudObject>>());
    }

    /**
     * Removes a given mud object from its Set and deletes the key if needed.
     *
     * @param object the object to remove
     * @return object the removed object
     */
    public MudObject removeMudObject(MudObject object) {

        final Map<String, Set<MudObject>> objectHashSets = getMudObjectHashSets();
        synchronized (objectHashSets) {
            Collection<Set<MudObject>> objects = objectHashSets.values();
            Iterator<Set<MudObject>> setIterator = objects.iterator();
            synchronized (objects) {
                while (setIterator.hasNext()) {
                    Set<MudObject> set = setIterator.next();
                    if (set.contains(object)) {
                        removeFromSetAndRemoveSetIfRequired(object, setIterator, set);
                    }
                }
            }
        }
        return object;
    }

    private void removeFromSetAndRemoveSetIfRequired(MudObject object, Iterator<Set<MudObject>> i, Set<MudObject> set) {
        set.remove(object);
        if (set.size() == 0) {
            i.remove();
        }
    }

    public Set<MudObject> getItemsOfClass(Class<?> clazz) {
        Set<MudObject> allObjects = new HashSet<MudObject>();

        final Map<String, Set<MudObject>> objectHashSets = getMudObjectHashSets();
        synchronized (objectHashSets) {
            Collection<Set<MudObject>> values = objectHashSets.values();
            Iterator<Set<MudObject>> x = values.iterator();
            while (x.hasNext()) {
                if (x.getClass().equals(clazz)) {
                    allObjects.addAll(x.next());
                }
            }
        }
        return new HashSet<MudObject>(allObjects);
    }

    /**
     * removes an object from the inventory at a given index
     *
     * @param objectName object to remove
     * @param index      index of object to remove
     * @return the removed object
     */
    public MudObject removeMudObject(String objectName, int index) {
        List<MudObject> list = new ArrayList<MudObject>(getMudObjectHashSets().get(objectName));

        return removeMudObject(list.get(index));
    }

    public void addAll(InventoryHandler handler) {
        this.mudObjectHashSets.putAll(handler.mudObjectHashSets);
    }

    public int size() {
        return getMudObjects().size();
    }

    /**
     * gives access to the backing treemap. the object are stored against their
     * name and shortnames as keys in this map.
     * <p/>
     * items with the same name will be in the hashset returned
     *
     * @return a treemap with the form mudobject.name, hashset<mudobject>
     */
    public Map<String, Set<MudObject>> getMudObjectHashSets() {
        return mudObjectHashSets;
    }

    public void clear() {
        Map<String, Set<MudObject>> map = getMudObjectHashSets();
        synchronized (map) {
            for (Set<MudObject> set : map.values()) {
                set.clear();
            }
            map.clear();
        }
    }

    public boolean contains(MudObject object) {
        return getMudObjects().contains(object);
    }

    public boolean containsAll(Collection<?> c) {
        return getMudObjects().containsAll(c);
    }
}